using System;
using System.Windows.Forms;

namespace nToolbox.WinForms
{
	/// <summary>
	/// This class is used to add the standard cut/copy/paste/etc. context menu items to a <see>ContextMenuStrip</see>.
	/// </summary>
	/// <remarks>
	/// <para>
	/// If a custom <see>ContextMenuStrip</see> is added to a text control, the standard editing operation will
	/// not be present in the menu. This class provides methods to add and handle these editing operations.
	/// </para>
	/// <para>
	/// Note: The Tag property of the ContextMenuStrip is used by this class.
	/// </para>
	/// </remarks>
	public static class EditContextMenu
	{
		/// <summary>
		/// Constants that determine which editing operation has been selected.
		/// </summary>
		private enum EditItems
		{
			None = 0,
			Cut,
			Copy,
			Clear,
			Undo,
			Paste,
			SelectAll
		}

		/// <summary>
		/// Handle the click event of an editing menu item.
		/// </summary>
		/// <param name="sender">The ToolStripItem that raised this event.</param>
		/// <param name="e">The <see>EventArgs</see> data for this event.</param>
		private static void DoEditMenu(object sender, EventArgs e)
		{
			ToolStripItem item = sender as ToolStripItem;
			if (item == null)
				return;


			ContextMenuStrip menu = item.Owner as ContextMenuStrip;
			if (menu == null)
				return;

			TextBoxBase textBox = TargetControl(menu);
			if (textBox == null) return;

			switch ((EditItems)item.Tag)
			{
				case EditItems.Cut: textBox.Cut(); break;
				case EditItems.Copy: textBox.Copy(); break;
				case EditItems.Clear: textBox.Clear(); break;
				case EditItems.Paste: textBox.Paste(); break;
				case EditItems.SelectAll: textBox.SelectAll(); break;
				case EditItems.Undo: textBox.Undo(); break;
			}
		}

		/// <summary>
		/// Adds the standard edit menu items to a new ContextMenuStrip, which is attached to the given control.
		/// </summary>
		/// <param name="textEditor">The text control to add a menu to.</param>
		public static void AddTo(TextBoxBase textEditor)
		{
			ContextMenuStrip menu = new ContextMenuStrip();
			menu.Tag = textEditor;
			AddTo(menu);
			textEditor.ContextMenuStrip = menu;
		}

		/// <summary>
		/// Helper function to create a <see>ToolStripMenuItem</see> with the given label and tag,
		/// with <see>DoEditMenu</see> as the click handler.
		/// </summary>
		private static ToolStripMenuItem MenuItem(string label, object tag)
		{
			ToolStripMenuItem item = new ToolStripMenuItem(label, null, DoEditMenu);
			item.Tag = tag;
			return item;
		}

		/// <summary>
		/// Adds the standard edit menu items to the given menu.
		/// </summary>
		private static void AddTo(ToolStripDropDown menu)
		{
			menu.Items.Add(MenuItem("&Undo", EditItems.Undo));
			menu.Items.Add(new ToolStripSeparator());
			menu.Items.Add(MenuItem("Cu&t", EditItems.Cut));
			menu.Items.Add(MenuItem("&Copy", EditItems.Copy));
			menu.Items.Add(MenuItem("&Paste", EditItems.Paste));
			menu.Items.Add(MenuItem("&Delete", EditItems.Clear));
			menu.Items.Add(new ToolStripSeparator());
			menu.Items.Add(MenuItem("Select &All", EditItems.SelectAll));

			menu.Opening += menu_Popup;
		}

		/// <summary>
		/// Handles the Opening event of a context menu. Enables and disables editing operations based on the
		/// contents and selection state of the associated text control, and the Windows clipboard.
		/// </summary>
		private static void menu_Popup(object sender, EventArgs e)
		{
			ContextMenuStrip menu = sender as ContextMenuStrip;
			if (menu == null) return;

			TextBoxBase textBox = TargetControl(menu);
			if (textBox == null) return;

			foreach (ToolStripItem mi in menu.Items)
			{
                if (!(mi.Tag is EditItems)) continue;

				switch ((EditItems)mi.Tag)
				{
					case EditItems.Copy:
						mi.Enabled = (textBox.SelectionLength > 0);
						break;

					case EditItems.Cut:
					case EditItems.Clear:
                        mi.Enabled = !textBox.ReadOnly && (textBox.SelectionLength > 0);
						break;

					case EditItems.Paste:
						mi.Enabled = !textBox.ReadOnly && (System.Windows.Forms.Clipboard.GetDataObject().GetDataPresent(DataFormats.Text));
						break;

					case EditItems.SelectAll:
                        mi.Enabled = (textBox.TextLength > 0);
						break;

					case EditItems.Undo:
                        mi.Enabled = (textBox.CanUndo);
						break;
				}
			}
		}

		/// <summary>
		/// Finds the target text control for the given context menu.
		/// </summary>
		/// <remarks>
		/// <para>
		/// This code works around a bug in .NET 1.1 where the SourceControl property was not always
		/// set properly. As a backup, the Tag of the context menu is also set to the text control.
		/// </para>
		/// <para>
		/// @TODO: Check if .NET 2.0 requires this workaround, or if the SourceControl is set correctly.
		/// </para>
		/// </remarks>
		private static TextBoxBase TargetControl(ContextMenuStrip menu)
		{
			TextBoxBase textBox = menu.SourceControl as TextBoxBase;
			if ((textBox == null) && (menu.Tag is TextBoxBase))
					textBox = menu.Tag as TextBoxBase;

			return textBox;
		}
	}
}
